package br.com.base.persistencia.filter.impl;

import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;

import javax.persistence.Query;

import br.com.base.business.impl.validatores.StringValidator;
import br.com.base.model.impl.EntidadeImpl;
import br.com.base.persistencia.filter.Filtro;
import br.com.base.persistencia.filter.impl.util.ClausulaAssociativaSQLEnum;
import br.com.base.persistencia.filter.impl.util.ClausulaComparativaSQLEnum;
import br.com.base.persistencia.filter.impl.util.ClausulaFuncaoSQL;
import br.com.base.persistencia.filter.impl.util.ClausulaOrderSQLEnum;
import br.com.base.persistencia.filter.impl.util.ParametroFiltro;
import br.com.base.persistencia.filter.impl.util.ParametroOrder;

/**
 * @author Milton Domingues
 */
public abstract class FiltroImpl<ENTIDADE extends EntidadeImpl> implements Filtro {


	private static final long serialVersionUID = -7702655567902368313L;

	private String alias;

	private Class<ENTIDADE> classEntidade;
	private Collection<ParametroFiltro> parametros;
	private Collection<ParametroOrder> parametrosOrder;

	private Integer inicio;
	private Integer quantidade;

	@SuppressWarnings("unchecked")
	public FiltroImpl(String alias) {
		this.alias = alias;
		this.parametros = new ArrayList<ParametroFiltro>();
		this.parametrosOrder = new ArrayList<ParametroOrder>();
		this.classEntidade = (Class<ENTIDADE>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
	}

	public void addBetween(String coluna, Object objeto1, Object objeto2) {
		this.addBetween(coluna, objeto1, objeto2, ClausulaAssociativaSQLEnum.AND);
	}

	public void addBetween(String coluna, Object objeto1, Object objeto2, ClausulaAssociativaSQLEnum associativa) {
		this.parametros.add(new ParametroFiltro(coluna, objeto1, objeto2, ClausulaComparativaSQLEnum.BETWEEN, associativa));
	}

	public void addEquals(String coluna, Object objeto) {
		this.addEquals(coluna, objeto, ClausulaAssociativaSQLEnum.AND);
	}

	public void addEquals(String coluna, Object objeto, ClausulaAssociativaSQLEnum associativa) {
		this.parametros.add(new ParametroFiltro(coluna, objeto, ClausulaComparativaSQLEnum.IQUAL, associativa));
	}

	public void addEqualsStringValid(String coluna, String valor){
		if(!StringValidator.isNull(valor)){
			this.addEquals(coluna, valor);
		}
	}

	public void addEqualsStringValid(String coluna, String valor, ClausulaAssociativaSQLEnum associativa){
		if(!StringValidator.isNull(valor)){
			this.addEquals(coluna, valor, associativa);
		}
	}

	public void addGt(String coluna, String value) {
		addGt(coluna, value, ClausulaAssociativaSQLEnum.AND);
	}

	public void addGt(String coluna, String value, ClausulaAssociativaSQLEnum associativa) {
		this.parametros.add(new ParametroFiltro(coluna, value, ClausulaComparativaSQLEnum.GREATER_THAN, associativa));
	}

	public void addIsNull(String coluna) {
		this.addIsNull(coluna, ClausulaAssociativaSQLEnum.AND);
	}

	public void addIsNull(String coluna, ClausulaAssociativaSQLEnum associativa) {
		this.parametros.add(new ParametroFiltro(coluna, ClausulaFuncaoSQL.IS_NULL, associativa));
	}

	public void addLike(String coluna, String objeto) {
		this.addLike(coluna, objeto, ClausulaAssociativaSQLEnum.AND);
	}

	public void addLike(String coluna, String objeto, ClausulaAssociativaSQLEnum associativa) {
		StringBuilder value = new StringBuilder();
		value.append("%");
		value.append(objeto);
		value.append("%");
		this.parametros.add(new ParametroFiltro(coluna, value.toString(), ClausulaComparativaSQLEnum.LIKE, associativa));
	}

	public void addLikeStringValid(String coluna, String valor) {
		if(!StringValidator.isNull(valor)){
			this.addLike(coluna, valor, ClausulaAssociativaSQLEnum.AND);
		}
	}

	public void addLikeStringValid(String coluna, String valor, ClausulaAssociativaSQLEnum associativa) {
		if(!StringValidator.isNull(valor)){
			this.addLike(coluna, valor, associativa);
		}
	}

	public void addLt(String coluna, String value) {
		addLt(coluna, value, ClausulaAssociativaSQLEnum.AND);
	}

	public void addLt(String coluna, String value, ClausulaAssociativaSQLEnum associativa) {
		this.parametros.add(new ParametroFiltro(coluna, value, ClausulaComparativaSQLEnum.LESS_THAN, associativa));
	}

	public void addNotEquals(String coluna, Object objeto) {
		this.addNotEquals(coluna, objeto, ClausulaAssociativaSQLEnum.AND);
	}

	public void addNotEquals(String coluna, Object objeto, ClausulaAssociativaSQLEnum associativa) {
		this.parametros.add(new ParametroFiltro(coluna, objeto, ClausulaComparativaSQLEnum.DIFERENTE, associativa));
	}

	public void addNotNull(String coluna) {
		this.addNotNull(coluna, ClausulaAssociativaSQLEnum.AND);
	}

	public void addNotNull(String coluna, ClausulaAssociativaSQLEnum associativa) {
		this.parametros.add(new ParametroFiltro(coluna, ClausulaFuncaoSQL.IS_NOT_NULL, associativa));
	}

	public void addOrder(String coluna, ClausulaOrderSQLEnum order){
		this.parametrosOrder.add(new ParametroOrder(coluna, order));
	}

	private void addSqlOrder(StringBuilder hql){

		if(this.getParametrosOrder().size() > 0){

			hql.append(" ORDER BY ");

			for (ParametroOrder para : this.getParametrosOrder()) {
				hql.append(para.getColuna());
				hql.append(" ");
				hql.append(para.getOrder());
				hql.append(" ");
			}
		}

	}

	private void addSqlWhere(StringBuilder hql) {

		for (ParametroFiltro para : this.getParametros()) {

			hql.append(para.getAssociativa());

			// Clausula com 1 parâmetro
			hql.append(" (");
			hql.append(this.getAlias());
			hql.append(".");
			hql.append(para.getColuna());
			hql.append(") ");

			if (para.getFuncao() == null) {

				hql.append(para.getComparativa());
				hql.append(" (:");
				hql.append(para.getColuna());
				hql.append(") ");

				// Clausula com 2 parâmetros
				if ((para.getComparativa() != null) && para.getComparativa().equals(ClausulaComparativaSQLEnum.BETWEEN)) {
					hql.append(ClausulaAssociativaSQLEnum.AND);
					hql.append(" (:");
					hql.append(para.getColuna());
					hql.append("2)");
				}

			} else {
				hql.append(para.getFuncao().getDescricao());
			}

		}
	}

	@Override
	public void clear(){
		this.getParametros().clear();
		this.getParametrosOrder().clear();
	}

	@Override
	public String getAlias() {
		return this.alias;
	}

	public Class<ENTIDADE> getClassEntidade() {
		return this.classEntidade;
	}

	@Override
	public String getHql() {

		StringBuilder hql = new StringBuilder();

		hql.append("FROM ");
		hql.append(this.getTargetClass());
		hql.append(" ");
		hql.append(this.getAlias());
		hql.append(" WHERE 1=1 ");

		this.addSqlWhere(hql);
		this.addSqlOrder(hql);


		return hql.toString();
	}

	@Override
	public Integer getInicio() {
		return this.inicio;
	}

	public Collection<ParametroFiltro> getParametros() {
		return this.parametros;
	}

	public Collection<ParametroOrder> getParametrosOrder() {
		return this.parametrosOrder;
	}

	@Override
	public Integer getQuantidade() {
		return this.quantidade;
	}

	private String getTargetClass() {
		return this.getClassEntidade().getSimpleName();
	}

	public void setAlias(String alias) {
		this.alias = alias;
	}

	public void setClassEntidade(Class<ENTIDADE> classEntidade) {
		this.classEntidade = classEntidade;
	}

	@Override
	public void setInicio(Integer inicio) {
		this.inicio = inicio;
	}

	public void setParametros(Collection<ParametroFiltro> parametros) {
		this.parametros = parametros;
	}

	@Override
	public void setParametros(Query query) {

		for (ParametroFiltro para : this.getParametros()) {
			if (para.getParametro() != null) {
				query.setParameter(para.getColuna(), para.getParametro());
				if (para.getComparativa().equals(ClausulaComparativaSQLEnum.BETWEEN)) {
					StringBuilder coluna = new StringBuilder();
					coluna.append(para.getColuna());
					coluna.append("2");
					query.setParameter(coluna.toString(), para.getParametro2());

				}
			}
		}
	}

	public void setParametrosOrder(Collection<ParametroOrder> parametrosOrder) {
		this.parametrosOrder = parametrosOrder;
	}

	@Override
	public void setQuantidade(Integer quantidade) {
		this.quantidade = quantidade;
	}

}
